package model;

import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RectangularShape;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import model.Player.PlayerState;
import model.commands.AbstractModelCommand;
import ctf.model.Side;
import ctf.model.TeamColor;
import ctf.network.ProtocolError;
import ctf.view.GameViewInterface;
/**
 * The games model.
 * @author N. Bhatia, A. Butt, N. Evans, C. Levesque, P. OKeefe
 *
 */
public class Model implements GameViewInterface, IModel, IPublishable {

	private int currentTime;
	private final Team leftTeam;
	private final Team rightTeam;
	private final RectangularShape field;
	private final Tree[] trees;
	private final String message;
	private List<AbstractModelCommand> commandList = Collections
	.synchronizedList(new LinkedList<AbstractModelCommand>());
	private ModelState modelState;
	private boolean leftSideInformed;
	private boolean rightSideInformed;
	private int gameEndReason;
	private Side whichTeamCaptured;
	private VisibilityChecker visChecker;


	public Model() {
		leftTeam = new Team(Side.LEFT);
		leftTeam.setColor(TeamColor.RED);
		rightTeam = new Team(Side.RIGHT);
		rightTeam.setColor(TeamColor.BLUE);
		field = new Rectangle2D.Double(0.0, 0.0,
				GameConstants.FIELD_WIDTH, GameConstants.FIELD_HEIGHT);
		trees = new Tree[GameConstants.NUM_TREES_PER_SIDE*2];
		modelState = ModelState.ROUND_ONGOING;
		message = new String();
		visChecker = new VisibilityChecker();
		currentTime = 0;
		leftSideInformed = false;
		rightSideInformed = false;
		initTrees();
		initializePlayerPositions();
	}


	private void initTrees() {
		if (GameConstants.TEST_MODE) {
			for (int i = 0; i < GameConstants.TREE_POSITIONS.length; i++) {
				trees[i] = new Tree(GameConstants.TREE_POSITIONS[i]);
			}
		} else { // Place random trees
			this.placeRandomTrees();
		}
	}


	private void initializePlayerPositions() {
		this.leftTeam.initializePlayerPositions(
				GameConstants.LEFT_TEAM_START_POSITIONS, 90.0);
		this.rightTeam.initializePlayerPositions(
				GameConstants.RIGHT_TEAM_START_POSITIONS, 270.0);
	}
	

	/**
	 * Randomly places trees within the games field while obeying the rules set by
	 * the CTF specification
	 */
	private void placeRandomTrees() {
		Random numGen = new Random();
		RectangularShape jail;
		// Place 10 trees per team
		for (int teams = 0; teams < GameConstants.MAX_CONNECTIONS; teams++) {
			for (int i = 0; i < trees.length; i++) {
				// Add left side
				int xPos, yPos;
				Point2D treeLoc;
				boolean toClose;
				// Ensure valid placements
				do {
					// Add offset for right team
					xPos = numGen.nextInt(249) + 1 + teams
					* (GameConstants.FIELD_WIDTH / 2);
					yPos = numGen.nextInt(249) + 1;
					toClose = false;
					treeLoc = new Point2D.Double(xPos, yPos);
					// Ensure not to close to players
					for (int j = GameConstants.NUM_PLAYERS_PER_SIDE - 1; (j >= 0)
					&& !toClose; j--) {
						Point2D[] startLocations = teams == 0 ? GameConstants.LEFT_TEAM_START_POSITIONS
								: GameConstants.RIGHT_TEAM_START_POSITIONS;
						toClose = treeLoc.distance(startLocations[j]) < 3 ? true
								: false;
					}
					// Ensure not close to other trees
					for (int k = trees.length - 1; (k >= 0) && !toClose; k--) {
						toClose = (trees[k].getPosition().distance(treeLoc) < 3) ? true
								: false;
					}
					jail = teams == 0 ? GameConstants.LEFT_TEAM_JAIL : GameConstants.RIGHT_TEAM_JAIL;
				} while (jail.contains(treeLoc)
						|| toClose);
				trees[i] = new Tree(treeLoc);
			}
		}
	}

	@Override
	public int clock() {
		return this.currentTime;
	}

	@Override
	public RectangularShape fieldSize() {
		return this.field;
	}

	@Override
	public Point2D flagPosition(Side side) {
		return getTeam(side).getFlag().getPosition();
	}

	@Override
	public RectangularShape jail(Side side) {
		return getTeam(side).getJail();
	}

	@Override
	public Collection<PlayerInfo> playerInfo(Side side) {
		return getTeam(side).getPlayerInfo();
	}

	@Override
	public double playerRadius() {
		return GameConstants.PLAYER_RADIUS;
	}

	@Override
	public int score(Side side) {
		return getTeam(side).getScore();
	}

	@Override
	public String statusMessage() {
		return this.message;
	}

	@Override
	public TeamColor teamColor(Side side) {
		return getTeam(side).getColor();
	}

	@Override
	public double treeRadius() {
		return GameConstants.TREE_RADIUS;
	}

	@Override
	public Collection<Point2D> trees() {
		Collection<Point2D> treePositions = new ArrayList<Point2D>();
		for (int i = 0; i < trees.length; i++) {
			treePositions.add(trees[i].getPosition());
		}
		return treePositions;
	}

	@Override
	public boolean update(int elapsedTimeInMillis) {
		elapsedTimeInMillis = (int) (elapsedTimeInMillis * GameConstants.CLOCK_MULTIPLIER);
		this.currentTime += elapsedTimeInMillis;
		this.executeCommands();
		GameEntityManager.Instance().update(elapsedTimeInMillis);
		stateTransitionsCheck();
		playerCollisionCheck();
		treeCollisionCheck();
		playersWithinGameBoundariesCheck();
		visChecker.update(leftTeam, rightTeam, trees);
//		this.updateVisibility();
		return (modelState == ModelState.ROUND_ONGOING) ? true : false;
	}
	
	/**
	 * Manages to transition of players between their states due to the environmental conditions.
	 */
	private void stateTransitionsCheck() {
		Player[] allPlayers = getAllPlayers();
		for (int i = 0; i < allPlayers.length; i++) {
			Player player = allPlayers[i];
			if (player.isOnOffendingSide() && player.getState() == PlayerState.DEFENDING) {
				player.setState(PlayerState.ATTACKING);
			}
			else if (player.isOnDefendingSide()){ //player is on defending side
				if (player.getState() == PlayerState.FLAG_CARRIER) {
					increaseScore(player.getSide(), 10);
					gameEndReason = GameConstants.GAME_END_FLAG_CAP;
					whichTeamCaptured = player.getSide();
					roundEnd();
				}
				else if (player.getState() == PlayerState.ATTACKING
						|| player.getState() == PlayerState.ESCAPEE) {
					player.setState(PlayerState.DEFENDING);
				}
			}
			//check for flag grabbing
			Team getEnemyTeam = getOtherTeam(player.getSide());
			Flag enemyFlag = getEnemyTeam.getFlag();
			if (player.getPosition().distance(enemyFlag.getPosition()) <= GameConstants.PLAYER_RADIUS
					&& player.getState() == PlayerState.ATTACKING && enemyFlag.hasNoCarrier()) {
				player.setState(PlayerState.FLAG_CARRIER);
				increaseScore(player.getSide(), 5);
				enemyFlag.pickedUpByPlayer(player);
			}
			//prisoners not in jail need to be jailed
			if (player.getState() == PlayerState.PRISONER) {
				jailPlayer(player);
			}
		}
	}


	private void roundEnd() {
		modelState = ModelState.ROUND_END;
		initializePlayerPositions();
		
	}

	/**
	 * Increases the teams score by an amount
	 * @param side The side which is receiving points.
	 * @param points The value of points awarded.
	 */
	private void increaseScore(Side side, int points) {
		this.getTeam(side).addToScore(points);
	}
	
	/**
	 * Executes the commands in the commandlist
	 */
	private void executeCommands() {
		int i = 0;
		while (i < commandList.size()
				&& commandList.get(i).getExecutionTime() <= this.getTime()) {
			commandList.get(i).execute(this);
			i++;
		}
		for (int j = 0; j < i; j++) {
			commandList.remove(0);
		}
	}

	@Override
	public int getTime() {
		return this.currentTime;
	}

	@Override
	public void setFlagPosition(Side side, Point2D flagPosition)
	throws ProtocolError {
		this.modelState = ModelState.ROUND_ONGOING;
		// -----Legal Zones
		RectangularShape legalLeftField = new Rectangle2D.Double(1, 1,
				GameConstants.FIELD_WIDTH / 2 - 2,
				GameConstants.FIELD_HEIGHT - 2);

		RectangularShape illegalLeftJailZone = new Rectangle2D.Double(
				GameConstants.LEFT_TEAM_JAIL.getX(),
				GameConstants.LEFT_TEAM_JAIL.getY(),
				GameConstants.LEFT_TEAM_JAIL.getWidth() + 1,
				GameConstants.LEFT_TEAM_JAIL.getHeight() + 1);

		RectangularShape legalRightField = new Rectangle2D.Double(
				GameConstants.FIELD_WIDTH / 2 + 1, 1,
				GameConstants.FIELD_WIDTH / 2 - 2,
				GameConstants.FIELD_HEIGHT - 2);

		RectangularShape illegalRightJailZone = new Rectangle2D.Double(
				GameConstants.RIGHT_TEAM_JAIL.getX() - 1,
				GameConstants.RIGHT_TEAM_JAIL.getY(),
				GameConstants.RIGHT_TEAM_JAIL.getWidth() + 2,
				GameConstants.RIGHT_TEAM_JAIL.getHeight() + 1);
		// -----/Legal Zones

		// Check static allowable zones
		if ((Side.LEFT == side) && legalLeftField.contains(flagPosition)
				&& !illegalLeftJailZone.contains(flagPosition)) {
			if (!nearPlayer(side, flagPosition)
					&& !nearTree(side, flagPosition)) {
				getTeam(side).getFlag().setPosition(flagPosition);
			} else {
				throw new ProtocolError("Illegal Flag Placement",
				"Flag to near tree or player");
			}

		} else if ((Side.RIGHT == side)
				&& legalRightField.contains(flagPosition)
				&& !illegalRightJailZone.contains(flagPosition)) {
			if (!nearPlayer(side, flagPosition)
					&& !nearTree(side, flagPosition)) {
				getTeam(side).getFlag().setPosition(flagPosition);
			} else {
				throw new ProtocolError("Illegal Flag Placement",
				"Flag to near tree or player");
			}
		} else {
			throw new ProtocolError("Illegal Flag Placement!",
			"Flag to near tree or player");
		}

	}

	private boolean nearPlayer(Side side, Point2D position) {
		Ellipse2D illegalRadius = new Ellipse2D.Double(position.getX(),
				position.getY(), GameConstants.ILLEGAL_FLAG_DIST_TO_PLAYER,
				GameConstants.ILLEGAL_FLAG_DIST_TO_PLAYER);
		Point2D[] playerArray = side == Side.LEFT ? GameConstants.LEFT_TEAM_START_POSITIONS
				: GameConstants.RIGHT_TEAM_START_POSITIONS;
		for (Point2D playerPoint : playerArray) {
			if (illegalRadius.contains(playerPoint)) {
				return true;
			}
		}
		return false;
	}

	private boolean nearTree(Side side, Point2D position) {
		Ellipse2D illegalRadius = new Ellipse2D.Double(position.getX(),
				position.getY(), GameConstants.ILLEGAL_FLAG_DIST_TO_OBJECT,
				GameConstants.ILLEGAL_FLAG_DIST_TO_OBJECT);
		for (Tree tree : this.trees) {
			if (illegalRadius.contains(tree.getPosition())) {
				return true;
			}
		}
		return false;
	}

	@Override
	public void placePlayer(Side side, int playerNum, Point2D position) {
		this.getTeam(side).getPlayer(playerNum).setPosition(position);
	}

	@Override
	public void setTime(int newTimeInMs) {
		this.currentTime = newTimeInMs;

	}

	@Override
	public void spinPlayer(Side side, int playerNum, float newAngularVelocity) {
		Player player = this.getTeam(side).getPlayer(playerNum);
		if (player != null) {
			player.setAngularVelocity(newAngularVelocity);
		}
	}

	private Team getTeam(Side side) {
		if (side == Side.LEFT) {
			return leftTeam;
		} else {
			return rightTeam;
		}
	}
	private Team getOtherTeam(Side side) {
		if (side == Side.LEFT) {
			return rightTeam;
		} else {
			return leftTeam;
		}
	}

	public void reset() {
		this.currentTime = 0;
		this.modelState = ModelState.ROUND_ONGOING;
		this.leftSideInformed = false;
		this.rightSideInformed = false;
		this.leftTeam.reset();
		initializePlayerPositions();
		this.rightTeam.reset();
		this.rightTeam.initializePlayerPositions(
				GameConstants.RIGHT_TEAM_START_POSITIONS, 270.0);
	}

	private void checkFlagExclusionZone(){
		Point2D leftTeamFlagPos = this.leftTeam.getFlag().getPosition();
		//left team flag exclusion zone check
		double distance = GameConstants.MAXIMUM_EXCLUSION_ZONE_RADIUS;
		Player[] rightTeamPlayers = rightTeam.getPlayers();
		for(int i = 0; i < GameConstants.NUM_PLAYERS_PER_SIDE; i++) {
			Player player = rightTeamPlayers[i];
			distance = Math.min(player.getPosition().distance(leftTeamFlagPos), distance);			
		}
		leftTeam.setFlagExclusionRadius(Math.min(distance, GameConstants.MAXIMUM_EXCLUSION_ZONE_RADIUS));

		Point2D rightTeamFlagPos = this.rightTeam.getFlag().getPosition();
		//left team flag exclusion zone check
		distance = GameConstants.MAXIMUM_EXCLUSION_ZONE_RADIUS;
		Player[] leftTeamPlayers = leftTeam.getPlayers();
		for(int i = 0; i < GameConstants.NUM_PLAYERS_PER_SIDE; i++) {
			Player player = leftTeamPlayers[i];
			distance = Math.min( player.getPosition().distance(rightTeamFlagPos), distance );
		}
		rightTeam.setFlagExclusionRadius(Math.min(distance, GameConstants.MAXIMUM_EXCLUSION_ZONE_RADIUS));
	}

	/**
	 * Helper method used to check collision of players on the same team. If two
	 * players positions overlap there speed and acceleration will be set to
	 * zero, and there position will be set to the position of the previous
	 * update call. Precondition: player array passed in must contain only
	 * players on the same team
	 * 
	 * @param players
	 *            - Array of players on the same team
	 */

	private void playerCollisionCheck() {
		Player[] allPlayers = getAllPlayers();
		ArrayList<Player> playersToJail = new ArrayList<Player>();
		checkFlagExclusionZone(); //Determine the new exclusion zones for each team

		for (int i = 0; i < allPlayers.length; i++) {
			Player player = allPlayers[i];
			Point2D position = player.getPosition();
			Team playerTeam = getTeam(player.getSide());
			double exclusionRadius = playerTeam.getFlagExclusionRadius();
			Point2D flagPosition = playerTeam.getFlag().getPosition();
			//Player is defending and attempting to enter into TCTF zone!! no way!
			if (position.distance(flagPosition) < exclusionRadius){
				//look at previous state to see if its in the exclusion zone
				Point2D prevPosition = player.getPreviousPosition();
				if (prevPosition.distance(flagPosition) > playerTeam.getFlagExclusionRadius()){
					player.revertToPreviousState();
				} else {// exclusion zone has grown to cover the player
					player.setState(PlayerState.TCTF);
				}
			}
			for (int j = i+1; j < allPlayers.length; j++) {				
				Player otherPlayer = allPlayers[j];
				//checkTCTF(player); //Handle players being trapped or leaving the exclusion zone.
				//checkTCTF(otherPlayer); //Handle players being trapped or leaving the exclusion zone.

				double otherPlayerDist = otherPlayer.getPosition().distance(player.getPosition());
				if (otherPlayerDist < GameConstants.PLAYER_RADIUS*2) {
					if(player.getState() == PlayerState.ATTACKING && otherPlayer.isOnDefendingSide()){
						playersToJail.add(player);
					}else if(player.isOnDefendingSide() && otherPlayer.getState() == PlayerState.ATTACKING){
						playersToJail.add(otherPlayer);
					}else if(player.isOnDefendingSide() && otherPlayer.getState() == PlayerState.FLAG_CARRIER){
						getTeam(player.getSide()).getFlag().drop();
						playersToJail.add(otherPlayer);						
					}else if(player.getState() == PlayerState.FLAG_CARRIER && otherPlayer.isOnDefendingSide()){
						getTeam(otherPlayer.getSide()).getFlag().drop();
						playersToJail.add(player);
					}else {
						player.revertToPreviousState();
						player.setAcceleration(0);
						player.setSpeed(0);
						otherPlayer.revertToPreviousState();
						otherPlayer.setAcceleration(0);
						otherPlayer.setSpeed(0);
					}
				}
			}

		}
		for (int i = 0; i < playersToJail.size(); i++) {
			jailPlayer(playersToJail.get(i));
		}
	}

	private Player[] getAllPlayers() {
		Player[] allPlayersArray = new Player[GameConstants.NUM_PLAYERS_PER_SIDE*2];
		Player[] leftTeamPlayers = leftTeam.getPlayers();
		Player[] rightTeamPlayers = rightTeam.getPlayers();
		for (int i = 0; i < GameConstants.NUM_PLAYERS_PER_SIDE; i++) {
			allPlayersArray[i] = leftTeamPlayers[i];
		}
		for (int i = 0, j = 8; i < GameConstants.NUM_PLAYERS_PER_SIDE; i++, j++) {
			allPlayersArray[j] = rightTeamPlayers[i];
		}
		return allPlayersArray;
	}


	/**
	 * Helper method to increase team score when players are jailed
	 * @param player the imprisoned player
	 */
	private void scoreForJail(Player player){
		Team capturedTeam = getTeam(player.getSide());
		Team capturingTeam = getOtherTeam(player.getSide());
		capturingTeam.addToScore(1);
		if(capturedTeam.isEntireTeamJailed()){
			capturingTeam.addToScore(2);				
			this.whichTeamCaptured = capturingTeam.getSide();
			this.gameEndReason = GameConstants.GAME_END_TEAM_CAP;
			this.modelState = ModelState.ROUND_END;
			roundEnd();
		}
	}

	/**
	 * Helper function used to check if any player has collided with a tree. If
	 * player collides with tree his position will be reverted to his position
	 * on the last update call, and his speed and acceleration will be set to 0.
	 * 
	 */
	private void treeCollisionCheck() {
		Player[] allPlayers = getAllPlayers();
		for(Player player: allPlayers){
			Ellipse2D playerCircle = new Ellipse2D.Double(player.getPosition().getX() - GameConstants.PLAYER_RADIUS,
					player.getPosition().getY() - GameConstants.PLAYER_RADIUS,
					GameConstants.PLAYER_RADIUS*2,
					GameConstants.PLAYER_RADIUS*2);

			for(Tree tree : this.trees){
				Point2D treePosition = tree.getPosition();
				Ellipse2D treeCircle = new Ellipse2D.Double(treePosition.getX() - GameConstants.TREE_RADIUS,
						treePosition.getY() - GameConstants.TREE_RADIUS,
						GameConstants.TREE_RADIUS*2,
						GameConstants.TREE_RADIUS*2);

				//Area class is used here since Ellipse2D class cannot be used to check intersection with other shapes
				Area treeCircleArea = new Area(treeCircle);
				Area playerCircleArea = new Area(playerCircle);
				treeCircleArea.intersect(playerCircleArea);

				if (!treeCircleArea.isEmpty()) {
					player.revertToPreviousState();
					player.setAcceleration(0);
					player.setSpeed(0);
				}
			}

		}
	}

	private void playersWithinGameBoundariesCheck() {
		Rectangle2D ctfField = new Rectangle2D.Double(0, 0,
				GameConstants.FIELD_WIDTH, GameConstants.FIELD_HEIGHT);
		Player[] allPlayers = getAllPlayers();

		for (Player player : allPlayers) {

			Ellipse2D playerCircle = new Ellipse2D.Double(player.getPosition()
					.getX()
					- GameConstants.PLAYER_RADIUS, player.getPosition().getY()
					- GameConstants.PLAYER_RADIUS,
					GameConstants.PLAYER_RADIUS * 2,
					GameConstants.PLAYER_RADIUS * 2);

			// checking to see if player is still within the ctf field
			if (!ctfField.contains(playerCircle.getBounds2D())) {
				player.revertToPreviousState();
				player.setAcceleration(0);
				player.setSpeed(0);
			}

			Area leftJailArea = new Area(GameConstants.LEFT_TEAM_JAIL);
			Area playerCircleArea = new Area(playerCircle);
			leftJailArea.intersect(playerCircleArea);
			// checking to see if player is in left jail zone
			if (!leftJailArea.isEmpty()
					&& player.getState() != PlayerState.ESCAPEE
					&& player.getState() != PlayerState.PRISONER) {
				//				player.revertToPreviousState();
				//				player.setAcceleration(0);
				//				player.setSpeed(0);
				if(player.getSide() == Side.RIGHT) {
					rightTeam.releasePlayer();
					rightTeam.addToScore(1);
					player.setState(PlayerState.ESCAPEE);
				}
			}

			Area rightJailArea = new Area(GameConstants.RIGHT_TEAM_JAIL);
			rightJailArea.intersect(playerCircleArea);
			// checking to see if player is in right jail zone
			if (!rightJailArea.isEmpty()
					&& player.getState() != PlayerState.ESCAPEE
					&& player.getState() != PlayerState.PRISONER) {
				//				player.revertToPreviousState();
				//				player.setAcceleration(0);
				//				player.setSpeed(0);
				if(player.getSide() == Side.LEFT){
					leftTeam.releasePlayer();
					leftTeam.addToScore(1);
					player.setState(PlayerState.ESCAPEE);
				}
			}
		}
	}

	@Override
	public void acceleratePlayer(Side side, int playerNum, float newAcceleration) {
		Player player = this.getTeam(side).getPlayer(playerNum);
		final float delayedAcceleration = newAcceleration;
		if (player != null) {
			player.setAcceleration(delayedAcceleration);
		}
	}

	@Override
	public void addCommand(AbstractModelCommand command) throws ProtocolError {
		command.checkValid(this);
		int i = 0;
		while (i < commandList.size()) {
			if (commandList.get(i).getExecutionTime() < command.getExecutionTime()) {
				i++;
			} else {
				break;
			}
		}
		commandList.add(i, command);
	}

	@Override
	public boolean playerExists(Side side, int playerNum) {
		boolean playerFound = false;
		Team team = getTeam(side);
		if (team != null) {
			Player player = team.getPlayer(playerNum);
			if (player != null) {
				playerFound = true;
			}
		}
		return playerFound;
	}
	private void jailPlayer(Player player) {
		Team teamOfJailedPlayer = getTeam(player.getSide());
		Team capturingTeam = getOtherTeam(player.getSide());
		boolean scored = teamOfJailedPlayer.jailPlayer(player, capturingTeam.getJail());
		if (scored) {
			scoreForJail(player);
		}
	}
		
	@Override
	public String publish(Side side) {
		String gameState = new String();
		if ((this.modelState == ModelState.ROUND_ONGOING)){
			gameState = "state: ";
			gameState += this.clock();
			gameState += this.rightTeam.publish(side);
			gameState += this.leftTeam.publish(side);
		}else if(modelState == ModelState.ROUND_END){

			if((side.equals(leftTeam.getSide())&&!leftSideInformed)){
				gameState = "roundOver: ";
				gameState += this.gameEndReason + " ";
				gameState += this.whichTeamCaptured == side ? 1 : 0 ;
				leftSideInformed = true;
			} else if((side.equals(rightTeam.getSide())&&!rightSideInformed)){
				gameState = "roundOver: ";
				gameState += this.gameEndReason + " ";
				gameState += this.whichTeamCaptured == side ? 1 : 0 ;
				rightSideInformed = true;
			} else{
				gameState = "state: ";
				gameState += this.clock();
				gameState += this.rightTeam.publish(Side.RIGHT);
				gameState += this.leftTeam.publish(Side.LEFT);
			}

			if(leftSideInformed && rightSideInformed)
				this.modelState = ModelState.ROUND_ONGOING;
		} else if(modelState == ModelState.GAME_OVER){
			gameState = "gameOver: ";
		}

		return gameState;

	}
	


	/**
	 * An enumeration to cover the states of the model and its handling of rounds.
	 * @author N. Bhatia, A. Butt, N. Evans, C. Levesque, P. OKeefe
	 *
	 */
	public enum ModelState{
		ROUND_ONGOING(0), ROUND_END(1), GAME_OVER(2);

		private int value;

		ModelState(int value){
			this.value = value;
		}
		public int value(){
			return this.value;
		}	
	}	
}
